﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Diagnostics;

namespace XchangeStreamer.Benchmark.Core
{
    public sealed class PerformanceMonitor : IDisposable
    {
        #region Private Fields

        private Timer timer;
        private float[,] collectedData;
        private int frequency; //seconds.
        private int totalTestTime; //seconds.
        private CounterInfo[] counters;
        private PerformanceCounter[] perfCounters;
        private bool started = false;
        private object locker = new object();
        private int lastIndex = -1;
        private int actualLastIndex = -1;
        private Action<CounterInfo[]> callback;

        #endregion

        #region Const.

        private PerformanceMonitor()
        { }

        public PerformanceMonitor(int frequencyInSeconds, int totalTestTimeInSeconds, Action<CounterInfo[]> callback, params CounterInfo[] counters)
        {
            if (callback == null)
            {
                throw new ArgumentNullException("Callback can't be null.");
            }

            if (counters == null)
            {
                throw new ArgumentNullException("Counters can't be null.");
            }

            if (counters.Length < 1)
            {
                throw new ArgumentOutOfRangeException("Counters must contains at least 1 counter.");
            }

            this.frequency = frequencyInSeconds;
            this.totalTestTime = totalTestTimeInSeconds;
            this.counters = counters;
            this.callback = callback;
        }

        #endregion

        #region Public Methods

        public void Start()
        {
            lock (this.locker)
            {
                if (this.started)
                {
                    throw new InvalidOperationException("Collecting performance counters already started.");
                }
                this.started = true;

                this.lastIndex = -1;
                this.actualLastIndex = -1;

                foreach (var counter in this.counters)
                {
                    counter.Average = float.NaN;
                    counter.Last = float.NaN;
                    counter.Maximum = float.NaN;
                    counter.Minimum = float.NaN;
                }

                this.perfCounters = new PerformanceCounter[this.counters.Length];

                for (int i = 0; i < this.perfCounters.Length; i++)
                {
                    this.perfCounters[i] = new PerformanceCounter(this.counters[i].CategoryName, this.counters[i].CounterName, this.counters[i].ProcessToMonitorName, true);
                }

                this.collectedData = new float[this.counters.Length, (this.totalTestTime / this.frequency)];

                GC.Collect();

                this.timer = new Timer(this.Collect, null, 0, 1000 * this.frequency);
            }
        }

        public void Stop()
        {
            lock (this.locker)
            {
                this.AutoStop(false);
            }
        }

        #endregion

        #region Private Methods

        private void Collect(object state)
        {
            int currentIndex = Interlocked.Increment(ref this.lastIndex);
            lock (this.locker)
            {
                if (!this.started)
                {
                    return;
                }

                if (currentIndex >= this.collectedData.GetUpperBound(1))
                {
                    this.AutoStop(true);
                }
                else
                {
                    for (int i = 0; i < this.perfCounters.Length; i++)
                    {
                        this.collectedData[i, currentIndex] = this.perfCounters[i].NextValue();
                    }
                    this.actualLastIndex = currentIndex;
                }
            }
        }

        private void AutoStop(bool auto)
        {
            if (!this.started)
            {
                if (auto)
                {
                    throw new InvalidOperationException("Collecting performance counters already auto stoped.");
                }
                else
                {
                    throw new InvalidOperationException("Collecting performance counters already stoped.");
                }
            }
            this.started = false;

            if (this.timer != null)
            {
                this.timer.Dispose();
                this.timer = null;
            }
            this.Calculate();
        }

        private void Calculate()
        {
            System.Threading.Tasks.Task.Factory.StartNew(() =>
            {
                float total, min, max;
                for (int i = 0; i < this.counters.Length; i++)
                {
                    total = 0;
                    min = float.MaxValue;
                    max = float.MinValue;
                    for (int j = 0; j < this.actualLastIndex; j++)
                    {
                        min = (min > this.collectedData[i, j]) ? this.collectedData[i, j] : min;
                        max = (max < this.collectedData[i, j]) ? this.collectedData[i, j] : max;
                        total += this.collectedData[i, j];
                    }

                    this.counters[i].Average = total / this.actualLastIndex;
                    this.counters[i].Last = this.collectedData[i, this.actualLastIndex];
                    this.counters[i].Maximum = max;
                    this.counters[i].Minimum = min;
                }

                this.collectedData = null;
                if (this.perfCounters != null)
                {
                    foreach (var perfCounter in this.perfCounters)
                    {
                        perfCounter.Close();
                        perfCounter.Dispose();
                    }
                    this.perfCounters = null;
                }

                this.callback(this.counters);
            });
        }

        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            throw new NotImplementedException();
        }

        #endregion
    }
}